﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;
using System.Security.AccessControl;
using System.Diagnostics;
using System.ServiceProcess;

namespace SetServiceSecurity
{
    /// <summary>
    /// Provides a way to set the ServiceRights for Windows services
    /// </summary>
    /// <remarks>
    /// Must run from an elevated command prompt using an account that has SeSecurity privilege.
    /// Does not currently provide for audit rules.
    /// Does not currently provide for deny rules.
    /// </remarks>
    internal class Program
    {
        private static Dictionary<string, ServiceController> InstalledServices { get; set; }
        private static Dictionary<string, ServiceRights> ServiceRightsList { get; set; }
        private static SecurityIdentifier AdministratorsSID { get; set; }
        private static SecurityIdentifier LocalSystemSID { get; set; }

        private static void Initialize()
        {

            InstalledServices = new Dictionary<string, ServiceController>(StringComparer.CurrentCultureIgnoreCase);
            ServiceController[] services = ServiceController.GetServices();
            foreach (var service in services)
            {
                if (!InstalledServices.ContainsKey(service.ServiceName))
                {
                    InstalledServices.Add(service.ServiceName, service);
                }
            }

            ServiceRightsList = new Dictionary<string, ServiceRights>(StringComparer.CurrentCultureIgnoreCase);
            foreach (ServiceRights serviceRight in Enum.GetValues(typeof(ServiceRights)))
            {
                if (!ServiceRightsList.ContainsKey(serviceRight.ToString()))
                {
                    ServiceRightsList.Add(serviceRight.ToString(), serviceRight);
                }
            }

            AdministratorsSID = new SecurityIdentifier("S-1-5-32-544");
            LocalSystemSID = new SecurityIdentifier("S-1-5-18");
        }

        private static void Main(string[] args)
        {

            if ((args == null) || (args.Length == 0)) { DisplayUsage(); return; }

            Initialize();

            // -l 
            if (args[0].StartsWith("-l", StringComparison.CurrentCultureIgnoreCase) && (args.Length == 1))
            {
                DisplayInstalledServicesInfo();
                return;
            }
            // -l serviceShortName
            else if (args[0].StartsWith("-l", StringComparison.CurrentCultureIgnoreCase) && (args.Length == 2))
            {
                if (args[0].StartsWith("-l", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (InstalledServices.ContainsKey(args[1]))
                    {
                        DisplayServiceAccessRules(args[1]);
                        return;
                    }
                }
            }
            // -d serviceShortName machineOrDomain\SecurityPrincipal
            else if (args[0].StartsWith("-d", StringComparison.CurrentCultureIgnoreCase) && (args.Length == 3))
            {
                DeleteServiceAccessRule(args[1], args[2]);
                return;
            }
            // -c serviceShortName machineOrDomain\SecurityPrincipal
            else if (args[0].StartsWith("-c", StringComparison.CurrentCultureIgnoreCase) && (args.Length == 3))
            {
                CheckServiceForAccessRule(args[1], args[2]);
                return;
            }
            // -a serviceShortName machineOrDomain\SecurityPrincipal right;right;right
            else if (args[0].StartsWith("-a", StringComparison.CurrentCultureIgnoreCase) && (args.Length == 4))
            {
                AddServiceAccessRule(args[1], args[2], args[3]);
                return;
            }

            DisplayUsage();
        }

        private static void DisplayUsage()
        {
            Console.WriteLine("Usage:{0}", Environment.NewLine);
            Console.WriteLine("SetServiceSecurity -l \r\n\t Enumerates all running services. ");
            Console.WriteLine("SetServiceSecurity -l serviceShortName \r\n\t Display discretionary ACEs for service");
            Console.WriteLine("SetServiceSecurity -a serviceShortName machineOrDomain\\SecurityPrincipal ServiceRight;ServiceRight \r\n\t Adds an ACE for a security principal for the list of service rights.");
            Console.WriteLine("SetServiceSecurity -d serviceShortName machineOrDomain\\SecurityPrincipal \r\n\t Removes an ACE for a security principal");
            Console.WriteLine("SetServiceSecurity -c serviceShortName machineOrDomain\\SecurityPrincipal \r\n\t Check if a service has an ACE for a security principal.");
            Console.WriteLine();
            Console.WriteLine("{0}Notes:{1}", Environment.NewLine, Environment.NewLine);
            Console.WriteLine("Will not add an ACE if an ACE for the security principal already exists.  Remove the existing ACE first.");
            Console.WriteLine("Will not remove ACE for LocalSystem or Administrators.");
            Console.WriteLine("Specify list of ServiceRights separated by a semicolon.  Available ServiceRights: {0}", Environment.NewLine);

            foreach (string name in Enum.GetNames(typeof(ServiceRights)))
            {
                Console.WriteLine("\t {0}", name);
            }
        }

        public static void DisplayInstalledServicesInfo()
        {
            foreach (var service in InstalledServices.Values)
            {
                Console.WriteLine("Service: {0}\r\n\t Display Name: {1}\r\n\t Type: {2}\r\n\t Status: {3}\r\n",
                    service.ServiceName, service.DisplayName, service.ServiceType, service.Status);
            }
        }

        public static void DisplayServiceAccessRules(string serviceShortName)
        {
            if (!InstalledServices.ContainsKey(serviceShortName))
            {
                Console.WriteLine("Service not found: {0}", serviceShortName);
                return;
            }

            ServiceSecurity serviceSecurity = Service.GetAccessControl(serviceShortName);
            AuthorizationRuleCollection accessRules = serviceSecurity.GetAccessRules(true, true, typeof(NTAccount));
            Console.WriteLine("Access rules for service: {0}", serviceShortName);
            foreach (ServiceAccessRule accessRule in accessRules)
            {
                Console.WriteLine("Identity: {0}\r\n\t  Allow/Deny: {1}\r\n\t  Inherited: {2}\r\n\t  ServiceRights: {3}",
                    accessRule.IdentityReference, accessRule.AccessControlType, accessRule.IsInherited, accessRule.ServiceRights);
            }

            Console.WriteLine();

            // display access and audit rules in SDDL format
            string accessRulesSDDL = serviceSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access);
            Console.WriteLine("AccessRules in SDDL: {0}", accessRulesSDDL);

            string auditRulesSDDL = serviceSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Audit);
            Console.WriteLine("AuditRules in SDDL: {0}", auditRulesSDDL);
        }
        /// <summary>
        /// Remove an access rule from a service
        /// </summary>
        /// <param name="serviceShortName"></param>
        /// <param name="securityPrincipalName">domainOrMachine\UserOrGroupName</param>
        /// <remarks>Only deletes explicit access rules, not inherited.</remarks>
        public static void DeleteServiceAccessRule(string serviceShortName, string securityPrincipalName)
        {
            if (!InstalledServices.ContainsKey(serviceShortName))
            {
                Console.WriteLine("Service not found: {0}", serviceShortName);
                return;
            }

            if (String.IsNullOrEmpty(securityPrincipalName) || securityPrincipalName.IndexOf(@"\") < 2)
            {
                Console.WriteLine(@"Security principal name: {0} is not in correct format.  Specify machineOrDomain\securityPrincipalName", securityPrincipalName);
                return;
            }
            string domain = securityPrincipalName.Substring(0, securityPrincipalName.IndexOf(@"\"));
            string username = securityPrincipalName.Substring(securityPrincipalName.IndexOf(@"\") + 1);
            if ((String.Compare(domain, "localhost", true) == 0) || (domain == ".")) domain = Environment.MachineName;

            NTAccount ntAccount = null;
            SecurityIdentifier sid = null;

            try
            {
                ntAccount = new NTAccount(domain, username);
                sid = ntAccount.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error attempting to resolve security principal: {0}", e);
            }

            if ((ntAccount == null) || (sid == null))
            {
                Console.WriteLine("Cannot update service due to: unable to resolve security principal: {0}", securityPrincipalName);
                return;
            }

            if (sid.Value == AdministratorsSID.Value)
            {
                Console.WriteLine("Not removing access rule for Administrators group.");
                return;
            }
            if (sid.Value == LocalSystemSID.Value)
            {
                Console.WriteLine("Not removing access rule for LocalSystem identifier.");
                return;
            }

            ServiceSecurity serviceSecurity = Service.GetAccessControl(serviceShortName);
            AuthorizationRuleCollection accessRules = serviceSecurity.GetAccessRules(true, false, typeof(SecurityIdentifier));
            ServiceSecurity newAccessRules = new ServiceSecurity();

            bool accessRuleRemoved = false;
            foreach (ServiceAccessRule accessRule in accessRules)
            {

                if (String.Compare(accessRule.IdentityReference.Value, sid.Value, StringComparison.CurrentCultureIgnoreCase) == 0)
                {
                    Console.WriteLine("Removing access rule.  Identity: {0} ServiceRights: {1}", accessRule.IdentityReference, accessRule.ServiceRights);
                    accessRuleRemoved = true;
                }
                else
                {
                    newAccessRules.AddAccessRule(accessRule);
                }
            }

            if (!accessRuleRemoved)
            {
                Console.WriteLine("Access rule for security principal: {0} on service: {1} not found.", securityPrincipalName, serviceShortName);
            }
            else
            {
                Console.Write("Updating access rules for service: {0}...", serviceShortName);
                Service.SetAccessControl(serviceShortName, newAccessRules);
                Console.Write("Done.");
                Console.WriteLine();
                DisplayServiceAccessRules(serviceShortName);
            }
        }

        /// <summary>
        /// Remove an access rule from a service
        /// </summary>
        /// <param name="serviceShortName"></param>
        /// <param name="securityPrincipalName">domainOrMachine\UserOrGroupName</param>
        /// <param name="serviceRights">one or more ServiceRights, separated by a semicolon</param></para>
        public static void AddServiceAccessRule(string serviceShortName, string securityPrincipalName, string serviceRights)
        {
            if (!InstalledServices.ContainsKey(serviceShortName))
            {
                Console.WriteLine("Service not found: {0}", serviceShortName);
                return;
            }

            if (String.IsNullOrEmpty(securityPrincipalName) || securityPrincipalName.IndexOf(@"\") < 2)
            {
                Console.WriteLine(@"Security principal name: {0} is not in correct format.  Specify machineOrDomain\securityPrincipalName", securityPrincipalName);
                return;
            }
            string domain = securityPrincipalName.Substring(0, securityPrincipalName.IndexOf(@"\"));
            string username = securityPrincipalName.Substring(securityPrincipalName.IndexOf(@"\") + 1);
            if ((String.Compare(domain, "localhost", true) == 0) || (domain == ".")) domain = Environment.MachineName;
            Debug.WriteLine(String.Format("Service: {0} machineOrDomain: {1} security principal: {2} Rights: {3}", serviceShortName, domain, username, serviceRights));

            NTAccount ntAccount = null;
            SecurityIdentifier sid = null;

            try
            {
                ntAccount = new NTAccount(domain, username);
                sid = ntAccount.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error attempting to resolve security principal: {0}", e);
            }

            if ((ntAccount == null) || (sid == null))
            {
                Console.WriteLine("Cannot update service due to: unable to resolve security principal: {0}", securityPrincipalName);
                return;
            }

            ServiceSecurity serviceSecurity = Service.GetAccessControl(serviceShortName);
            AuthorizationRuleCollection accessRules = serviceSecurity.GetAccessRules(true, false, typeof(SecurityIdentifier));


            bool accessRuleExists = false;
            foreach (ServiceAccessRule accessRule in accessRules)
            {
                if (String.Compare(accessRule.IdentityReference.Value, sid.Value, StringComparison.CurrentCultureIgnoreCase) == 0)
                {
                    accessRuleExists = true;
                    Console.WriteLine("Not adding access rule due to access rule already exists for service: {0} Identity: {1} ServiceRights: {2}",
                        serviceShortName, accessRule.IdentityReference.Translate(typeof(NTAccount)).Value, accessRule.ServiceRights);
                    break;
                }
            }

            if (!accessRuleExists)
            {
                ServiceRights? rights = null;
                List<string> rightsList = new List<string>(serviceRights.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries));
                foreach (var right in rightsList)
                {
                    if (ServiceRightsList.ContainsKey(right))
                    {
                        if (!rights.HasValue)
                        {
                            rights = ServiceRightsList[right];
                        }
                        else
                        {
                            rights |= ServiceRightsList[right];
                        }
                    }
                    else
                    {
                        Console.Write("Not adding access rules for service: {0} Identity: {1} Due to invalid ServiceRight specified: {2}", 
                            serviceShortName, ntAccount.Value, right);
                        return;
                    }
                }

                AccessControlType accessControlType = AccessControlType.Allow;
                ServiceAccessRule accessRule = new ServiceAccessRule(sid, rights.Value, accessControlType );
                Console.WriteLine("Adding access rule: Service: {0} Identity: {1} ServiceRights: {2} AccessControlType: {3}",
                    serviceShortName, ntAccount.Value, serviceRights, accessControlType);
                serviceSecurity.AddAccessRule(accessRule);

                Console.Write("Updating access rules for service: {0}...", serviceShortName);
                Service.SetAccessControl(serviceShortName, serviceSecurity);
                Console.Write("Done.");
                Console.WriteLine();
                DisplayServiceAccessRules(serviceShortName);
            }
        }

        /// <summary>
        /// Check a service to determine if it has an access rule for the specified security principal
        /// </summary>
        /// <param name="serviceShortName"></param>
        /// <param name="securityPrincipalName">domainOrMachine\UserOrGroupName</param>
        /// <remarks>Checks for both explicit and inherited access rules.</remarks>
        public static bool CheckServiceForAccessRule(string serviceShortName, string securityPrincipalName)
        {
            return CheckServiceForAccessRule(serviceShortName, securityPrincipalName, false);
        }
        public static bool CheckServiceForAccessRule(string serviceShortName, string securityPrincipalName, bool supressOutput)
        {
            if (!InstalledServices.ContainsKey(serviceShortName))
            {
                if (!supressOutput) Console.WriteLine("Service not found: {0}", serviceShortName);
                return false;
            }

            if (String.IsNullOrEmpty(securityPrincipalName) || securityPrincipalName.IndexOf(@"\") < 2)
            {
                if (!supressOutput) Console.WriteLine(@"Security principal name: {0} is not in correct format.  Specify machineOrDomain\securityPrincipalName", securityPrincipalName);
                return false;
            }
            string domain = securityPrincipalName.Substring(0, securityPrincipalName.IndexOf(@"\"));
            string username = securityPrincipalName.Substring(securityPrincipalName.IndexOf(@"\") + 1);

            NTAccount ntAccount = null;
            SecurityIdentifier sid = null;

            try
            {
                ntAccount = new NTAccount(domain, username);
                sid = ntAccount.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;
            }
            catch (IdentityNotMappedException e) { }
            catch (Exception e)
            {
                Console.WriteLine("Error attempting to resolve security principal: {0}", e.Message);
            }

            if ((ntAccount == null) || (sid == null))
            {
                Console.WriteLine("Cannot update service due to: unable to resolve security principal: {0}", securityPrincipalName);
                return false;
            }

            ServiceSecurity serviceSecurity = Service.GetAccessControl(serviceShortName);
            AuthorizationRuleCollection accessRules = serviceSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier));

            bool accessRuleExists = false;
            foreach (ServiceAccessRule accessRule in accessRules)
            {
                if (String.Compare(accessRule.IdentityReference.Value, sid.Value, StringComparison.CurrentCultureIgnoreCase) == 0)
                {
                    Console.WriteLine("Found access rule.  Identity: {0} Inherited: {1} ServiceRights: {2}",
                        accessRule.IdentityReference.Translate(typeof(NTAccount)).Value, accessRule.IsInherited, accessRule.ServiceRights);
                    accessRuleExists = true;
                }
            }

            if (!accessRuleExists)
            {
                Console.WriteLine("Access rule for security principal: {0} on service: {1} not found.", securityPrincipalName, serviceShortName);
            }

            return accessRuleExists;
        }
    }
}
